(function (window, T) {
    var sio = T.sio = {};
    
    /**
     * 
     * @param {HTMLElement} src script节点
     * @param {String} url script节点的地址
     * @param {String} [charset] 编码
     */
    sio._createScriptTag = function(scr, url, charset){
        scr.setAttribute('type', 'text/javascript');
        charset && scr.setAttribute('charset', charset);
        scr.setAttribute('src', url);
        document.getElementsByTagName('head')[0].appendChild(scr);
    };

    /**
     * 删除script的属性，再删除script标签，以解决修复内存泄漏的问题
     * 
     * @param {HTMLElement} src script节点
     */
    sio._removeScriptTag = function(scr){
        if (scr.clearAttributes) {
            scr.clearAttributes();
        } else {
            for (var attr in scr) {
                if (scr.hasOwnProperty(attr)) {
                    delete scr[attr];
                }
            }
        }
        if(scr && scr.parentNode){
            scr.parentNode.removeChild(scr);
        }
        scr = null;
    };


    /**
     * 通过script标签加载数据，加载完成由浏览器端触发回调
     * @name sio.callByBrowser
     * @function
     * @grammar sio.callByBrowser(url, opt_callback, opt_options)
     * @param {string} url 加载数据的url
     * @param {Function|string} opt_callback 数据加载结束时调用的函数或函数名
     * @param {Object} opt_options 其他可选项
     * @config {String} [charset] script的字符集
     * @config {Integer} [timeOut] 超时时间，超过这个时间将不再响应本请求，并触发onfailure函数
     * @config {Function} [onfailure] timeOut设定后才生效，到达超时时间时触发本函数
     * @remark
     * 1、与callByServer不同，callback参数只支持Function类型，不支持string。
     * 2、如果请求了一个不存在的页面，callback函数在IE/opera下也会被调用，因此使用者需要在onsuccess函数中判断数据是否正确加载。
     * @meta standard
     * @see sio.callByServer
     */
    sio.callByBrowser = function (url, opt_callback, opt_options) {
        var scr = document.createElement("SCRIPT"),
            scriptLoaded = 0,
            options = opt_options || {},
            charset = options['charset'],
            callback = opt_callback || function(){},
            timeOut = options['timeOut'] || 0,
            timer;
        
        // IE和opera支持onreadystatechange
        // safari、chrome、opera支持onload
        scr.onload = scr.onreadystatechange = function () {
            // 避免opera下的多次调用
            if (scriptLoaded) {
                return;
            }
            
            var readyState = scr.readyState;
            if ('undefined' == typeof readyState
                || readyState == "loaded"
                || readyState == "complete") {
                scriptLoaded = 1;
                try {
                    callback();
                    clearTimeout(timer);
                } finally {
                    scr.onload = scr.onreadystatechange = null;
                    sio._removeScriptTag(scr);
                }
            }
        };

        if( timeOut ){
            timer = setTimeout(function(){
                scr.onload = scr.onreadystatechange = null;
                sio._removeScriptTag(scr);
                options.onfailure && options.onfailure();
            }, timeOut);
        }
        
        sio._createScriptTag(scr, url, charset);
    };

    /**
     * 通过script标签加载数据，加载完成由服务器端触发回调
     * @name sio.callByServer
     * @function
     * @grammar sio.callByServer(url, callback[, opt_options])
     * @param {string} url 加载数据的url.
     * @param {Function|string} callback 服务器端调用的函数或函数名。如果没有指定本参数，将在URL中寻找options['queryField']做为callback的方法名.
     * @param {Object} opt_options 加载数据时的选项.
     * @config {string} [charset] script的字符集
     * @config {string} [queryField] 服务器端callback请求字段名，默认为callback
     * @config {Integer} [timeOut] 超时时间(单位：ms)，超过这个时间将不再响应本请求，并触发onfailure函数
     * @config {Function} [onfailure] timeOut设定后才生效，到达超时时间时触发本函数
     * @remark
     * 如果url中已经包含key为“options['queryField']”的query项，将会被替换成callback中参数传递或自动生成的函数名。
     * @meta standard
     * @see sio.callByBrowser
     */
    sio.callByServer = /**@function*/function(url, callback, opt_options) {
        var scr = document.createElement('SCRIPT'),
            prefix = 'bd__cbs__',
            callbackName,
            callbackImpl,
            options = opt_options || {},
            charset = options['charset'],
            queryField = options['queryField'] || 'callback',
            timeOut = options['timeOut'] || 0,
            timer,
            reg = new RegExp('(\\?|&)' + queryField + '=([^&]*)'),
            matches;

        if (lang.isFunction(callback)) {
            callbackName = prefix + Math.floor(Math.random() * 2147483648).toString(36);
            window[callbackName] = getCallBack(0);
        } else if(lang.isString(callback)){
            // 如果callback是一个字符串的话，就需要保证url是唯一的，不要去改变它
            // TODO 当调用了callback之后，无法删除动态创建的script标签
            callbackName = callback;
        } else {
            if (matches = reg.exec(url)) {
                callbackName = matches[2];
            }
        }

        if( timeOut ){
            timer = setTimeout(getCallBack(1), timeOut);
        }

        //如果用户在URL中已有callback，用参数传入的callback替换之
        url = url.replace(reg, '\x241' + queryField + '=' + callbackName);
        
        if (url.search(reg) < 0) {
            url += (url.indexOf('?') < 0 ? '?' : '&') + queryField + '=' + callbackName;
        }
        sio._createScriptTag(scr, url, charset);

        /*
         * 返回一个函数，用于立即（挂在window上）或者超时（挂在setTimeout中）时执行
         */
        function getCallBack(onTimeOut){
            /*global callbackName, callback, scr, options;*/
            return function(){
                try {
                    if( onTimeOut ){
                        options.onfailure && options.onfailure();
                    }else{
                        callback.apply(window, arguments);
                        clearTimeout(timer);
                    }
                    window[callbackName] = null;
                    delete window[callbackName];
                } catch (exception) {
                    // ignore the exception
                } finally {
                    sio._removeScriptTag(scr);
                }
            }
        }
    };

    /**
     * 通过请求一个图片的方式令服务器存储一条日志
     * @function
     * @grammar sio.log(url)
     * @param {string} url 要发送的地址.
     * @author: int08h,leeight
     */
    sio.log = function(url) {
      var img = new Image(),
          key = 'tangram_sio_log_' + Math.floor(Math.random() *
                2147483648).toString(36);

      // 这里一定要挂在window下
      // 在IE中，如果没挂在window下，这个img变量又正好被GC的话，img的请求会abort
      // 导致服务器收不到日志
      window[key] = img;

      img.onload = img.onerror = img.onabort = function() {
        // 下面这句非常重要
        // 如果这个img很不幸正好加载了一个存在的资源，又是个gif动画
        // 则在gif动画播放过程中，img会多次触发onload
        // 因此一定要清空
        img.onload = img.onerror = img.onabort = null;

        window[key] = null;

        // 下面这句非常重要
        // new Image创建的是DOM，DOM的事件中形成闭包环引用DOM是典型的内存泄露
        // 因此这里一定要置为null
        img = null;
      };

      // 一定要在注册了事件之后再设置src
      // 不然如果图片是读缓存的话，会错过事件处理
      // 最后，对于url最好是添加客户端时间来防止缓存
      // 同时服务器也配合一下传递Cache-Control: no-cache;
      img.src = url;
    };
})(window, T);